package org.fhnw.aigs.commons.communication;
import java.io.*;
import java.net.Socket;
import java.util.logging.Level;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import org.fhnw.aigs.commons.LogRouter;
import org.fhnw.aigs.commons.Player;
import org.fhnw.aigs.commons.XMLHelper;
/**
* This is the base class for all custom Message classes. It provides an
* interface which will be used by the mechanisms behind the AIGS and AIGS
* BaseClient. Every message carries the information on who sent the message (in
* the case of a client-→server message) or who will receive the message
* (server-→client). Additionally every message carries the fully qualified name
* of the class, e.g. "org.fhnw.aigs.commons.JoinMessage" which allows the
* server or client to load the class.<br>
* Every message can have as many additional attributes as necessary. They will
* automatically be translated into XML. The marshalling and unmarshalling is
* handled by JAXB.<br>
* Please make sure that:<br><ul>
* <li>Every message class must have a default, zero-argument constructor!</li>
* <li>Every message class must be annotated with the
*
* {@literal @}XmlElementRoot attribute in the following
* way: <code>@XmlRootElement(name="YourClassName")</code></li>
* <li>Every field is set to private.</li>
* <li>Every attribute that is not a primitive datatype <b>MUST HAVE</b> an
* empty zero-argument constructor.</li>
* <li>Every field possesses a getter and setter which has EXACTLY this
* structure:
* <b>get[CORRESPONDING ATTRIBUTE NAME]</b>, <b>set[CORRESPONDINGATTRIBUTE]</b>
* name. So the attribute <b>isRed</b> has a setter <b>setIsRed</b> and a getter
* <b>getIsRed</b>.<li>
* <li>Every getter and setter is annotated with the
* {@literal @}XmlElement annotation. The name attribute of the annotation is the name that
* will be used when marshalled to xml. If this has not been done JAXB will
* assume a name for the element.</li>
* <li>Every array field must be annotated with the
* {@literal @}XmlElementWrapper annotation. The name attribute of the annotation is the
* parent element's name, the
* {@literal @}XmlElement's name attribute is the child's name in the marshalled XML
* form.</li></ul><br>
* v1.0 Initial release<br>
* v1.1 Functional changes<br>
* v1.2 Changing of logging<br>
* v1.3 Fixed issues with malformed input for the xml logger
* @author Matthias Stöckli (v1.0)
* @version 1.3 (Raphael Stoeckli, 23.04.2015)
*/
public abstract class Message {
/**
* Every message carries the information to and from which message the
* message will be sent.
*/
protected Player player;
/**
* Every message <b>must</b> possess an empty constructor due to the
* mechanisms of how JAXB, the framework which takes care of the XML
* marshalling and unmarshalling
*/
public Message() {
}
/**
* Gets the fully qualified class name of the message.
*/
@XmlAttribute(name = "FullyQualifiedClassName")
public String getFullyQualifiedName() {
return getClass().getCanonicalName();
}
/**
* See {@link player}
*/
@XmlElement(name = "Player")
public Player getPlayer() {
return player;
}
/**
* See {@link player}
*/
public void setPlayer(Player player) {
this.player = player;
}
/**
* Sends a message to a player using a socket.<br>
* Usually a call to {@link org.fhnw.aigs.commons.Game#sendMessageToPlayer} or
* {@link org.fhnw.aigs.commons.Game#sendMessageToAllPlayers} would be the preferred method.<br>
* Hack inserted since v1.1 → Problem with ClassLoader
*
* @param socket The player's socket.
* @param player The receiving player.
*/
public void send(Socket socket, Player player)
{
// HACK: The very first call of a running game causes a crash in marshaller. All further attemps running without problems. Problem with ClassLoader???
boolean processingError = false;
String errorMessage = "";
Exception ex1 = null;
for(int i = 0; i < 5; i++) // In case of an exception, let's try n times and then give up (and handle error message)
{
try {
// It is not possible to send a message without a socket
if (socket == null) {
throw new IOException("No socket available - could not send message!");
}
this.player = player;
// Marshal/turn the message into XML and create a {@link StringWriter} out of it.
Marshaller marshaller = XMLHelper.getUnformattedXMLMarshaller(this);
StringWriter sw = new StringWriter();
marshaller.marshal(this, sw);
// Write the message to a {@link OutputStreamWriter} / {@link PrintWriter}
// and print the message to the client. Then send/flush the writer.
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
String xmlString = sw.toString();
writer.println(xmlString);
writer.flush();
//Logger.getLogger(Message.class.getName()).log(Level.INFO, "=> \n {0}", XMLHelper.prettyPrintXml(xmlString));
LogRouter.log(Message.class.getName(), Level.INFO, "=> \n {0}", XMLHelper.prettyPrintXml(xmlString, false));
processingError = false;
break;
}
catch (JAXBException ex)
{
errorMessage = "Message could not be parsed.";
ex1 = ex;
processingError = true;
}
catch (IOException ex)
{
errorMessage = "Could not send the message, socket is missing. Game will be terminated.";
ex1 = ex;
processingError = true;
}
catch (Exception ex) // All other errors
{
errorMessage = "An unknown error occurred.";
ex1 = ex;
processingError = true;
}
}
if (processingError == true) // none of the n attemps was successful
{
//Logger.getLogger(Message.class.getName()).log(Level.SEVERE, errorMessage, ex1);
LogRouter.log(Message.class.getName(), Level.SEVERE, errorMessage, ex1);
}
}
/**
* Turns a plain text xml message in a StringReader into a message object.
* Usually it should not be necessary to call this method as the messages
* will be parsed by the server and base clients already. However this
* method may be used to create custom xml-parsing implementations.
*
* @param <T> The desired class.
* @param reader A {@link StringReader} containing the message's text.
* @param messageClass The desired class.
* @return The parsed message.
*/
public static <T extends Message> Message parse_OLD(StringReader reader, Class<T> messageClass) {
Message parsedMessage = null;
try {
parsedMessage = JAXB.unmarshal(reader, messageClass);
} catch (NoSuchMethodError ex) {
parsedMessage =
new ForceCloseMessage("The class " + messageClass.getSimpleName()
+ " is erroneous. Please check the annotations.");
}
return parsedMessage;
}
public static <T extends Message> Message parse(StringReader reader, Class<T> messageClass) {
Message parsedMessage = null;
boolean processingError = false;
// HACK: The very first call of a running game causes a crash in unmarshal. All further attemps running without problems. Problem with ClassLoader???
for(int i = 0; i < 5; i++) // In case of an exception, let's try n times and then give up (and handle error message)
{
try
{
parsedMessage = JAXB.unmarshal(reader, messageClass);
processingError = false;
break;
}
catch (NoSuchMethodError ex)
{
processingError = true;
}
catch(Exception ex) // All other exceptions
{
processingError = true;
}
}
if (processingError == true)
{
parsedMessage = new ForceCloseMessage("The class " + messageClass.getSimpleName() + " is erroneous. Please check the annotations.");
}
return parsedMessage;
}
}